package main

import (
	"fmt"
	"reflect"
)

/**
 * 用于输出一个非接口类型的方法集合
 **/
func dumpMethodSet(i interface{}) {
	dynTyp := reflect.TypeOf(i)

	if dynTyp == nil {
		fmt.Println("there is no dynamic type")
		return
	}

	n := dynTyp.NumMethod()
	if n == 0 {
		fmt.Printf("%s's method set is empty!\n", dynTyp)
		return
	}

	fmt.Printf("%s's method set:\n", dynTyp)
	for j := 0; j < n; j++ {
		fmt.Println("-", dynTyp.Method(j).Name)
	}
	fmt.Print("\n")
}

type T struct {
	a int
}

// Go函数的参数采用的是值拷贝传递
// 当receiver的参数类型为T时, 对参数的修改不会影响到原实例, 只会影响到传递到的副本
func (t T) M1() {
	t.a = 10
}

// 当receiver的参数类型是*T时, 因为参数是指针, 修改会影响到原来的实例
func (t *T) M2() {
	t.a = 11
}

// 无论是T类型实例, 还是 *T 类型实例, 都既可以调用receiver为T类型的方法, 也可以调用receiver为*T类型的方法
func main() {
	var t T
	println(t.a) // 0

	t.M1()
	println(t.a) // 0

	t.M2()       // T实例类型可以调用receiver参数类型为*T的方法M2, 都是Go编译器在背后自动进行转换的结果(语法糖)
	println(t.a) // 11

	var t2 = &T{}
	println(t2.a) // 0

	t2.M1()
	println(t2.a) // 0

	t2.M2()
	println(t.a) // 11

	// 一般情况下, 通常为receiver参数选择T类型, 因为这样可以缩窄外部修改类型实例内部状态的"接触面", 也就是少暴露可以修改类型内部状态的方法
	// 不过, 也有例外情况需要特别注意. 考虑到Go方法调用时, receiver参数时以值拷贝的形式传入方法中的。那么, 如果receiver参数类型的size较大, 以值拷贝形式传入就会导致较大的性能开销, 这时我们选择*T作为receiver类型可能更好些。

	// 方法集合

	type Interface interface {
		M1()
		M2()
	}

	var pt *T
	var i Interface

	pt = &t
	i = pt
	// i = t // cannot use t (type T) as type Interface in assignment:
	// T does not implement Interface (M2 method has pointer receiver)
	// T 没有实现Interface类型方法列表中的M2, 因此类型T的实例t不能赋值给Interface变量

	// 方法集合也是用来判断一个类型是否实现了某接口类型的唯一手段。 可以说, "方法集合决定了接口实现"
	// 如果某类型T得方法集合与某接口类型的方法集合相同, 或者类型T得方法集合时接口类型I方法集合的超集, 那么就说这个类型T实现了接口I.
	// 或者说, 方法集合这个概念在Go语言中的主要用途, 就是用来判断某个类型是否实现了某个接口

	i.M1()

	var n int
	dumpMethodSet(n)
	dumpMethodSet(&n)

	dumpMethodSet(t) // T 类型的方法集合没有接口中的M2方法, 所以不能赋值
	dumpMethodSet(&t)

	// 如果T类型需要实现某个接口, 就要使用T作为receiver参数的类型, 来满足接口类型方法集合中的所有方法。

	type S T
	var s S
	dumpMethodSet(s)
	dumpMethodSet(&s)
}
